package cn.sc.summer.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.sc.summer.gateway.context.GatewayContext;
import cn.sc.summer.gateway.util.IPUtil;
import com.alibaba.fastjson.JSONObject;
import cn.sc.summer.constant.project.DateConstant;
import cn.sc.summer.constant.project.ProjectConstant;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.model.Result;
import cn.sc.summer.constant.warmup.WarmUpConstant;
import cn.sc.summer.token.encrypt.TokenUtil;
import cn.sc.summer.token.enums.RequestTypeEnum;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * 请求内容存储 处理请求内容 内容放在gatewayContext中
 */
@Slf4j
@Component
public class RequestCoverFilter implements GlobalFilter, Ordered {

    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getPath().pathWithinApplication().value();

        if (StrUtil.isNotBlank(path) && path.equals(WarmUpConstant.WARM_UP)) {
            response.setStatusCode(HttpStatus.OK);
            DataBuffer buffer = response.bufferFactory().wrap(JSONObject.toJSONString(Result.success()).getBytes());
            return response.writeWith(Mono.just(buffer));
        }
        HttpHeaders headers = request.getHeaders();

        ServerHttpRequest.Builder mutate = request.mutate();
        String rawPath = request.getURI().getRawPath();

        mutate.header(TokenConstant.REQUEST_TYPE, RequestTypeEnum.GATEWAY_NORMAL.getType());
        mutate.header(TokenConstant.API_KEY_REQUEST_HEADER, TokenUtil.getApiEncryptKey(16, ProjectConstant.SERVER_NAME, RequestTypeEnum.GATEWAY_NORMAL));

        GatewayContext gatewayContext = new GatewayContext();
        gatewayContext.setPath(path);
        gatewayContext.getFormData().addAll(request.getQueryParams());
        gatewayContext.setIpAddress(String.valueOf(request.getRemoteAddress()));
        gatewayContext.setHeaders(headers);

        // 获得路由
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

        // 注意，因为webflux的响应式编程 不能再采取原先的编码方式 即应该先将gatewayContext放入exchange中，否则其他地方可能取不到
        exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);

        StringBuilder stringBuilder = new StringBuilder("\n\tGateway request report --------------- ")
                .append(new SimpleDateFormat(DateConstant.YYYY_MM_DD_HH_MM_SS_1).format(new Date()));
        stringBuilder.append("\n\tType \t: ").append(RequestTypeEnum.GATEWAY_NORMAL.getType());
        stringBuilder.append(" / ").append(request.getMethod());
        stringBuilder.append("\t[ http://").append(IPUtil.getIpAddr(request)).append(" ]");
        stringBuilder.append("\n\tSever \t: ").append(route.getUri().toString());
        stringBuilder.append("  [ ").append(rawPath).append(" ]");
        stringBuilder.append("\n\t----------------------------------------------------------");
        log.info(stringBuilder.toString());

        // 处理参数
        MediaType contentType = headers.getContentType();
        long contentLength = headers.getContentLength();
        if (contentLength > 0) {
            if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                return readBody(exchange, chain, gatewayContext);
            }
            if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
                return readFormData(exchange, chain, gatewayContext);
            }
        }

        return chain.filter(exchange);
    }

    /**
     * ReadFormData
     *
     * @param exchange 交换机
     * @param chain    过滤链
     * @return 今日下一个过滤器
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {

        final ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        return exchange.getFormData().doOnNext(gatewayContext::setFormData).then(Mono.defer(() -> {
            Charset charset = headers.getContentType().getCharset();
            charset = charset == null ? StandardCharsets.UTF_8 : charset;
            MultiValueMap<String, String> formData = gatewayContext.getFormData();

            if (CollUtil.isEmpty(formData)) {
                return chain.filter(exchange);
            }
            StringBuilder formDataBodyBuilder = new StringBuilder();

            formData.forEach((key, value) -> formDataBodyBuilder.append(key).append(" = ").append(value));
            String formDataBodyString = formDataBodyBuilder.toString();

            byte[] bodyBytes = formDataBodyString.getBytes(charset);
            int contentLength = bodyBytes.length;

            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {

                @NonNull
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                @NonNull
                @Override
                public Flux<DataBuffer> getBody() {
                    return DataBufferUtils.read(new ByteArrayResource(bodyBytes),
                            new NettyDataBufferFactory(ByteBufAllocator.DEFAULT), contentLength);
                }
            };
            ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build();

            return chain.filter(mutateExchange);
        }));
    }

    /**
     * ReadJsonBody
     *
     * @param exchange 交换机
     * @param chain    过滤器链
     * @return 进入下一个过滤器
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
        return DataBufferUtils.join(exchange.getRequest().getBody()).publishOn(Schedulers.boundedElastic()).flatMap(dataBuffer -> {

            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);

            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.release(buffer);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                @NonNull
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };

            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class)
                    .doOnNext(gatewayContext::setCacheBody).then(chain.filter(mutatedExchange));

        });
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

}

